package common

import play.api.libs.json._

/**
 * Representation of a response from a REST api call.  Contains meta data as well as the optional
 * response payload if there was no error
 */
sealed trait ApiResponse[T] {

  def statusCode: Int

  def message: String
}

object ApiResponse {

  case class Ok[T](
      statusCode: Int = 200,
      message: String = "OK",
      response: Option[T] = None
  ) extends ApiResponse[T]

  object Ok {

    implicit def jsonFormat[T](implicit fmt: Format[T]): Format[Ok[T]] = Json.format[Ok[T]]
  }

  case class Error[T](
      statusCode: Int = 400,
      message: String = "FAIL",
      error: T
  ) extends ApiResponse[T]

  object Error {

    implicit def jsonFormat[T](implicit fmt: Format[T]): Format[Error[T]] = Json.format[Error[T]]
  }

  implicit def jsonFormat[T](implicit fmt: Format[T]): Format[ApiResponse[T]] =
    new Format[ApiResponse[T]] {

      def reads(json: JsValue): JsResult[ApiResponse[T]] =
        Reads.JsObjectReads.reads(json).map(_.fields).flatMap {
          case Seq(("statusCode", JsNumber(statusCode)), _, _) ⇒
            statusCode.toInt match {
              case 200 ⇒ Json.fromJson[Ok[T]](json)
              case 400 ⇒ Json.fromJson[Error[T]](json)
              case _ ⇒
                JsError(
                  JsonValidationError("error.json.validation.ApiResponse.statusCode", statusCode)
                )
            }
          case _ ⇒
            JsError(JsonValidationError("error.json.validation.ApiResponse"))
        }

      def writes(o: ApiResponse[T]): JsValue = o match {
        case r: Ok[T]    ⇒ Ok.jsonFormat(fmt).writes(r)
        case r: Error[T] ⇒ Error.jsonFormat(fmt).writes(r)
      }
    }
}
